//---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// File: TokenizerHelper.cs
//
// Description: This file contains the implementation of TokenizerHelper.
//              This class should be used by most - if not all - MIL parsers.
//
// History:
//  05/19/2003 : Microsoft - Created it
//  05/20/2003 : Microsoft - Moved to Shared
//
//---------------------------------------------------------------------------

using System;
using System.Globalization;

namespace WPFTemplateLib.Utilities
{
	/// <summary>
	/// 来自：HandyControl.Tools
	/// </summary>
	internal class TokenizerHelper
	{
		private char _quoteChar;

		private char _argSeparator;

		private string _str;

		private int _strLen;

		private int _charIndex;

		private int _currentTokenIndex;

		private int _currentTokenLength;

		public bool FoundSeparator { get; private set; }

		public TokenizerHelper(string str, IFormatProvider formatProvider)
		{
			var numberSeparator = GetNumericListSeparator(formatProvider);
			Initialize(str, '\'', numberSeparator);
		}

		private void Initialize(string str, char quoteChar, char separator)
		{
			_str = str;
			_strLen = str?.Length ?? 0;
			_currentTokenIndex = -1;
			_quoteChar = quoteChar;
			_argSeparator = separator;

			// immediately forward past any whitespace so
			// NextToken() logic always starts on the first
			// character of the next token.
			while (_charIndex < _strLen)
			{
				if (!char.IsWhiteSpace(_str, _charIndex))
				{
					break;
				}

				++_charIndex;
			}
		}

		public string GetCurrentToken() =>
			_currentTokenIndex < 0 ? null : _str.Substring(_currentTokenIndex, _currentTokenLength);

		internal bool NextToken() => NextToken(false);

		public bool NextToken(bool allowQuotedToken) => NextToken(allowQuotedToken, _argSeparator);

		public bool NextToken(bool allowQuotedToken, char separator)
		{
			_currentTokenIndex = -1; // reset the currentTokenIndex
			FoundSeparator = false; // reset

			// If we're at end of the string, just return false.
			if (_charIndex >= _strLen)
			{
				return false;
			}

			var currentChar = _str[_charIndex];

			// setup the quoteCount
			var quoteCount = 0;

			// If we are allowing a quoted token and this token begins with a quote,
			// set up the quote count and skip the initial quote
			if (allowQuotedToken &&
				currentChar == _quoteChar)
			{
				quoteCount++; // increment quote count
				++_charIndex; // move to next character
			}

			var newTokenIndex = _charIndex;
			var newTokenLength = 0;

			// loop until hit end of string or hit a , or whitespace
			// if at end of string ust return false.
			while (_charIndex < _strLen)
			{
				currentChar = _str[_charIndex];

				// if have a QuoteCount and this is a quote
				// decrement the quoteCount
				if (quoteCount > 0)
				{
					// if anything but a quoteChar we move on
					if (currentChar == _quoteChar)
					{
						--quoteCount;

						// if at zero which it always should for now
						// break out of the loop
						if (0 == quoteCount)
						{
							++_charIndex; // move past the quote
							break;
						}
					}
				}
				else if (char.IsWhiteSpace(currentChar) || currentChar == separator)
				{
					if (currentChar == separator)
					{
						FoundSeparator = true;
					}
					break;
				}

				++_charIndex;
				++newTokenLength;
			}

			// if quoteCount isn't zero we hit the end of the string
			// before the ending quote
			if (quoteCount > 0)
			{
				throw new InvalidOperationException("TokenizerHelperMissingEndQuote");
			}

			ScanToNextToken(separator); // move so at the start of the nextToken for next call

			// finally made it, update the _currentToken values
			_currentTokenIndex = newTokenIndex;
			_currentTokenLength = newTokenLength;

			if (_currentTokenLength < 1)
			{
				throw new InvalidOperationException("TokenizerHelperEmptyToken");
			}

			return true;
		}

		private void ScanToNextToken(char separator)
		{
			// if already at end of the string don't bother
			if (_charIndex >= _strLen) return;

			var currentChar = _str[_charIndex];

			// check that the currentChar is a space or the separator.  If not
			// we have an error. this can happen in the quote case
			// that the char after the quotes string isn't a char.
			if (currentChar != separator && !char.IsWhiteSpace(currentChar))
			{
				throw new InvalidOperationException("TokenizerHelperExtraDataEncountered");
			}

			// loop until hit a character that isn't
			// an argument separator or whitespace.
			// !!!Todo: if more than one argSet throw an exception
			var argSepCount = 0;
			while (_charIndex < _strLen)
			{
				currentChar = _str[_charIndex];

				if (currentChar == separator)
				{
					FoundSeparator = true;
					++argSepCount;
					_charIndex++;

					if (argSepCount > 1)
					{
						throw new InvalidOperationException("TokenizerHelperEmptyToken");
					}
				}
				else if (char.IsWhiteSpace(currentChar))
				{
					++_charIndex;
				}
				else
				{
					break;
				}
			}

			// if there was a separatorChar then we shouldn't be
			// at the end of string or means there was a separator
			// but there isn't an arg

			if (argSepCount > 0 && _charIndex >= _strLen)
			{
				throw new InvalidOperationException("TokenizerHelperEmptyToken");
			}
		}

		internal static char GetNumericListSeparator(IFormatProvider provider)
		{
			var numericSeparator = ',';

			// Get the NumberFormatInfo out of the provider, if possible
			// If the IFormatProvider doesn't not contain a NumberFormatInfo, then
			// this method returns the current culture's NumberFormatInfo.
			var numberFormat = NumberFormatInfo.GetInstance(provider);

			// Is the decimal separator is the same as the list separator?
			// If so, we use the ";".
			if (numberFormat.NumberDecimalSeparator.Length > 0 && numericSeparator == numberFormat.NumberDecimalSeparator[0])
			{
				numericSeparator = ';';
			}

			return numericSeparator;
		}
	}
}
